#ifndef _XX_YY_BMOCK_TEST_MODE_CONTROL
#define _XX_YY_BMOCK_TEST_MODE_CONTROL
#include "detail/bmock_any.hpp"
#include <boost/shared_ptr.hpp>
#include <boost/type_traits.hpp>
#include <boost/regex.hpp>
#include <boost/mpl/or.hpp>
#include <boost/utility/enable_if.hpp>
#include <ctime>
#include <stack>
#include <list>
#include <set>
#include <string>
#include <iostream>
#include "bmock_expectation.hpp"
#include "bmock_control_tools.hpp"
#include "bmock_message.hpp"
#include "detail/bmock_func_info.hpp"
#include "detail/bmock_mutex.hpp"
#include "detail/bmock_strict_mocks.hpp"
#include "detail/bmock_non_strict_mocks.hpp"

namespace bmock {	
	template<typename T>
	struct is_ptr_or_ref : public boost::mpl::or_<
		boost::is_pointer<T>,boost::is_reference<T> 
	>{};

	struct test_mode_control
	{					
		test_mode_control(const location &,const control_tools_ptr &);
		void       init(const location &);
		~test_mode_control();
		void replay(const location &);
		void verify(const location &);
		static test_mode_control *get_instance();
		bool terminate();
		void set_result(const location &,const result *const r);
		void set_void_result(const location &);
		void use_void_ptrs_only();		
		void expect_call_range(int min_, int max_);
		void expect_call_range(int min_, const bmock::infinite_calls& max_);		
		void check(const func_spec &f,const arg_list &args);
		void return_void(const location &loc);
		void bad_return_type();
		void set_stub_recording_state();		
		void set_non_strict_recording_state();
		void validate_expect();
		void connect(const expectation::slot_type &,const location &);
		void add_mock_type(boost::shared_ptr<mocks_type> mt);		

		bool mock_defined(const std::string& v) const;
		bool exact_mock_defined(std::string func_decl) const;
		control_tools_ptr get_tools(){return tools_;}
		void define_mock(const std::string& v);		

		void compare_args_to_func_sig(const std::vector<bmock::argument>& macro_args, const std::vector<std::string>& func_args, const location& loc) const;

		bmock_any get_new_result();
		static boost::any* store_ret_val_data(boost::any v);

		struct state_test {
			virtual void       replay(test_mode_control *) const;
			virtual void       set_stub_recording_state(test_mode_control *) const;			
			virtual void       set_non_strict_recording_state(test_mode_control *) const;
			virtual void       check(test_mode_control *,const func_spec &,const arg_list &) const;
			virtual void       verify(test_mode_control *) const;
			virtual void       terminate(test_mode_control *) const;
			virtual bool       check_terminate(test_mode_control *) const;
			virtual void       set_result(test_mode_control *) const;
			virtual bmock_any get_result(test_mode_control *) const;
			virtual void       return_void(test_mode_control *,const location &) const;
			virtual void       validate_expect(test_mode_control *) const;
			virtual void       connect(test_mode_control *,const expectation::slot_type &) const;
			static void        init(test_mode_control *);

			void               error(test_mode_control *,error_code) const;
		};
	protected:
		friend struct state_test;
		friend struct st_recording;
		friend struct st_rec_empty;
		friend struct st_rec_non_empty;
		friend struct st_rec_waiting_check;
		friend struct st_rec_waiting_get_return;
		friend struct st_replaying;
		friend struct st_rep_non_empty;
		friend struct st_rep_waiting_get_return;
		friend struct st_rep_empty;
		friend struct st_final;
		friend struct st_error;
		friend struct st_recording_stub;
		friend struct st_rec_stub_waiting_check;		
		friend struct st_recording_non_strict;
		friend struct st_rec_non_strict_waiting_check;

		void       set_location(const char *,size_t);
		bmock_any get_result_value() const;
		void       add_expectation();
		void       add_stub();		
		void	   add_non_strict();		
		void       not_received();
		void       next_line();                      
		void       prev_line();
		void       check_call(const func_spec &,const arg_list &);
		void       check_stub_call(const func_spec &,const arg_list &);		
		void       expect_call(const func_spec &,const arg_list &);
		void       expect_stub_call(const func_spec &,const arg_list &);
		void	   expect_non_strict_call(const func_spec &,const arg_list &); 
		bool       is_empty() const;
		bool	   is_done() const;
		bool       stub_is_empty() const;
		bool	   non_strict_is_empty() const;
		void       save_result();
		void       save_stub_result();
		void	   save_non_strict_result();
		void	   connect_callback(const expectation::slot_type &);
		void       unexpected_call(const func_spec &,const arg_list &);
		void       unexpected_call_before_replay(const func_spec &,const arg_list &);
		void       state_error(error_code,const location &);
		void       state_error(error_code);
		void       set_state(const state_test *const st);
		void       abort(const location &) const;
		void	   post_check_ops(exp_ptr p_e, mocks_type* mock_type_ds_ptr, 
			const func_spec& f, const arg_list& args, bool call_in_mock_area);

		const state_test			         *state_;
		std::list<boost::shared_ptr<mocks_type> >	mocked_area_types_;
		non_strict_mocks	non_strict_calls_;
		std::map<location, exp_ptr>		stub_calls_;
		exp_ptr						new_stub_;
		exp_ptr						last_expectation_stub_;
		bool                          typed_pointers_;
		result_ptr                    p_result_;	
		static const result_ptr		  p_void_;
		
		boost::regex	turn_to_regex(std::string v);
		bool			is_exact_mock(const std::string& str) const;
		void			insert_exact_mock(std::string str);
		
		static std::stack<test_mode_control *>  instances_;
		control_tools_ptr             tools_;
		std::vector<boost::regex>	  mocks_;
		std::vector<std::string> exact_mocks_;		
		location                      loc_;
	};

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::enable_if<boost::is_same<EXP_TYPE, ACT_TYPE>, value_result*>::type
		__make_value_result_ref(EXP_TYPE v)
	{		
		return new value_result(bmock_any(v));
	}

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::enable_if_c<!boost::is_same<EXP_TYPE, ACT_TYPE>::value && boost::is_convertible<EXP_TYPE, ACT_TYPE>::value, value_result*>::type
		__make_value_result_ref(EXP_TYPE v)
	{		
		return new value_result(bmock_any((ACT_TYPE)v));
	}

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::disable_if_c<boost::is_same<EXP_TYPE, ACT_TYPE>::value || boost::is_convertible<EXP_TYPE, ACT_TYPE>::value, value_result*>::type
		__make_value_result_ref(EXP_TYPE v)
	{
		bmock::test_mode_control::get_instance()->bad_return_type();
		return new value_result(bmock_any(v));
	}

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::enable_if<boost::is_same<EXP_TYPE, ACT_TYPE>, value_result*>::type
		__make_value_result_non_ref(EXP_TYPE v)
	{	
		ACT_TYPE* at = boost::any_cast<ACT_TYPE>(test_mode_control::store_ret_val_data(v));		
		return new value_result(bmock_any(at));
	}

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::enable_if_c<!boost::is_same<EXP_TYPE, ACT_TYPE>::value && boost::is_convertible<EXP_TYPE, ACT_TYPE>::value, value_result*>::type
		__make_value_result_non_ref(EXP_TYPE v)
	{		
		ACT_TYPE* at = boost::any_cast<ACT_TYPE>(test_mode_control::store_ret_val_data((ACT_TYPE)v));		
		return new value_result(bmock_any(at));
	}

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::disable_if_c<boost::is_same<EXP_TYPE, ACT_TYPE>::value || boost::is_convertible<EXP_TYPE, ACT_TYPE>::value, value_result*>::type
		__make_value_result_non_ref(EXP_TYPE v)
	{
		bmock::test_mode_control::get_instance()->bad_return_type();
		return new value_result(bmock_any(v));
	}
	
	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::enable_if<boost::is_reference<ACT_TYPE>, value_result*>::type
		make_value_result(EXP_TYPE& v) 
	{
		return __make_value_result_ref<typename boost::remove_reference<ACT_TYPE>::type*, EXP_TYPE*>(&v);
	}

	template <typename ACT_TYPE, typename EXP_TYPE>	
	typename boost::disable_if<boost::is_reference<ACT_TYPE>, value_result*>::type
		make_value_result(EXP_TYPE v) 
	{
		return __make_value_result_non_ref<ACT_TYPE, EXP_TYPE>(v);
	}	
};

#endif
